package han.cirno.corrupt.components.cclib

import han.cirno.corrupt.util.getBodyStringAndClose
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStream
import kotlin.math.min

class CCLib {
    companion object{
        val instance=CCLib()
        const val INDEX_PATH="index.meta"
    }
    private val okHttpClient=OkHttpClient()

    suspend fun networkFetchFile(name: String,outPath:String,callback: (Int, Int) -> Unit):Result<Unit>{
        return withContext(Dispatchers.IO) {
            val file = File(outPath, name).also { it.delete();it.createNewFile() }
            val fileOutputStream = FileOutputStream(file)
            return@withContext mNetworkFetch(name, fileOutputStream, callback).also { fileOutputStream.close()}
        }
    }

    suspend fun networkFetch(name:String, outputStream: OutputStream, callback:(Int, Int)->Unit):Result<Unit>{
        return withContext(Dispatchers.IO){
            return@withContext mNetworkFetch(name, outputStream, callback)
        }
    }

    private fun mNetworkFetch(name:String, outputStream: OutputStream, callback:(Int, Int)->Unit):Result<Unit>{
        try {
            callback(-1,-1)
            repairFile(name, outputStream) getter@ { md5, index, o ->
                okHttpClient.newCall(
                    Request.Builder()
                        .url("https://gitee.com/CCirno/corrupt-res/raw/master/dst/$md5")
                        .build()
                ).execute().use {
                    val sizePerFile = 1024 * 1024 - 1
                    val responseBody = it.body ?: throw IOException("Network error")
                    val inputStream = responseBody.byteStream()
                    val bufferLength =
                        if (o == null)
                            sizePerFile
                        else
                            if (o.subList.size == 1 || o.subList.size - 1 == index) o.lastSize
                            else sizePerFile
                    val byteArray = ByteArray(bufferLength)
                    var length = 0
                    while (inputStream.read(byteArray, length, min(bufferLength - length, 1024)).also {r-> length += r } != -1)
                        if (index != -1)
                            callback(index * sizePerFile + length, sizePerFile * (o!!.subList.size - 1) + o.lastSize)
                    return@getter byteArray
                }
            }
            return Result.success(Unit)
        }catch (e:Exception){
            e.printStackTrace()
            return Result.failure(e)
        }
    }

    suspend fun getManifest():Result<String>{
        return withContext(Dispatchers.IO) {
            try {
                val response = okHttpClient.newCall(
                    Request.Builder()
                        .url("https://gitee.com/CCirno/corrupt-res/raw/master/dst/$INDEX_PATH")
                        .build()
                ).execute().getBodyStringAndClose()?:throw Exception("Empty body")
                return@withContext Result.success(response)
            } catch (e: Exception) {
                return@withContext Result.failure(e)
            }
        }
    }

    private fun repairFile(
        name: String,
        outputStream: OutputStream,
        contentGetter: (String, Int, FileObject?) -> ByteArray
    ){
        val fileObject=getFileObject(name,String(contentGetter(INDEX_PATH,-1,null)))
        fileObject.subList.forEachIndexed {i,it->
            outputStream.write(contentGetter(it,i,fileObject))
        }
    }

    fun getFileObject(name:String,names:String):FileObject{
        return names.split("\n").find {
            val fileObject = repairToFileObject(it)
            fileObject.name==name
        }!!.let { repairToFileObject(it) }
    }

    private fun repairToFileObject(entry:String):FileObject{
        val list = entry.split("\\")
        val name=list[0]
        val lastSize=list[1]
        val md5sString=list[2]
        val md5StringList=ArrayList<String>()
        for(i in md5sString.indices step 32){
            md5StringList.add(md5sString.substring(i,i+32))
        }
        return FileObject(name,md5StringList,lastSize.toInt())
    }

    data class FileObject(val name:String,val subList: List<String> =ArrayList(),val lastSize:Int)
}